<template>
  <div>
    <el-upload action class="file-item upload-item" :show-file-list="false" :accept="accept" :limit="limit" :before-upload="beforeUpload" :http-request="emptyFn" :disabled="readonly" :dir="dir" >
      <div slot="trigger">
        <div v-if="!fileList.length " class="item-el pointer box-column box-center lightgray">
          <i class="el-icon-plus fz_20"></i>
          <div class="pt_10">
            {{ isPic ? '上传图片' : '上传文件' }}
          </div>
        </div>
        <template v-if="isPic">
          <img class="item-el" v-for="(src, index) in images" :src="src" :key="index" width="40" height="40" />
        </template>
        <template v-else>
          <div class="item-el text box-column box-center" v-for="(item, index) in fileList" :key="index">
            {{ item.name }}
          </div>
        </template>
      </div>
    </el-upload>
    <el-input v-show="false" :value.sync="value"></el-input>
    <template v-if="isPic">
      <div class="img-el" :class="{ show: isShow }">
        <video id="videoCamera" :width="videoWidth" :height="videoHeight" autoplay></video>
        <div class="tc">
          <el-button type="primary" @click="setImage">拍照</el-button>
        </div>
      <i class="icon el-icon-circle-close" @click="closeCompetence"></i>
      </div>
      <canvas id="canvasCamera" style="display:none;" :width="videoWidth" :height="videoHeight"></canvas>
      <el-button @click="getCompetence">打开摄像头</el-button>
    </template>
  </div>
</template>
<script>
  import { find, floor, isEmpty, concat, compact, map, filter } from 'lodash';
  import { oosUpload } from '@/utils/oos';
  import { linkVerify } from '@/utils';
  import 'viewerjs/dist/viewer.css';
  // import { component as viewer } from 'v-viewer';
  export default {
    name: 'UxUpload',
    model: {
      prop: 'value',
      event: 'updateValue'
    },
    props: {
      // 是否只读(为true时只做展示)
      isPic: {
        type: Boolean,
        default: true
      },
      // 是否只读(为true时只做展示)
      readonly: {
        type: Boolean,
        default: false
      },
      // 表单绑定值(保证用法与普通表单组件一致)
      value: {
        type: String,
        default: ''
      },
      // 存储路径
      dir: {
        type: String,
        default: ''
      },
      // 最大允许上传文件数量(isSingle为flase时生效)
      // 默认为2是为了避免单文件上传时无法继续选择
      limit: {
        type: Number,
        default: 2
      },
      // 允许上传文件大小(单位b)
      maxSize: {
        type: Number,
        default: 1024 * 1024 * 10
      },
      // 允许上传类型（默认为已配置全部类型）
      // 可用类型由rules配置决定
      acceptType: {
        type: Array,
        default: function() {
          return ['pic'];
        }
      },
      // 是不是单文件上传
      isSingle: {
        type: Boolean,
        default: true
      },
      // 文件名称（阿里云中文件名称基本上都是随机串，这里可以设置一个别名来显示）（只适用于单文件上传）
      fileName: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        oos: 'https://oss.park.hy-access.com/',
        // 上传文件列表
        fileList: [],
        // 文件格式配置
        rules: [
          {
            name: 'xlsx',
            // 校验规则
            rule: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel'],
            // 文件后缀
            suffix: ['.xlsx', '.xls']
          },
          {
            name: 'doc',
            rule: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword'],
            suffix: ['.doc', '.docx']
          },
          {
            name: 'pdf',
            rule: ['application/pdf'],
            suffix: ['.pdf']
          },
          {
            name: 'pic',
            rule: ['image/jpeg', 'image/jpg', 'image/png'],
            suffix: ['.jpeg', '.jpg', '.png']
          },
          {
            name: 'ppt',
            rule: ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
            suffix: ['.ppt', '.pptx']
          },
          {
            name: 'txt',
            rule: ['text/plain'],
            suffix: ['.txt']
          }
        ],
        // 允许上传类型(根据rules自动生成)
        accept: '',
        // 运行上传类型校验规则(根据rules自动生成)
        acceptRules: [],
        // 允许上传类型提示文案(根据rules自动生成)
        acceptMessage: '',
        // 标识是否正在设置数据，避免重复触发事件
        isUp: false,
        images: [],
        videoWidth: 300,
        videoHeight: 300,
        imgSrc: '',
        thisCancas: null,
        thisContext: null,
        thisVideo: null,
        isShow: false
      };
    },
    watch: {
      // 监听表单绑定值
      // 将字符串数据转换为可识别的数据格式
      value: {
        immediate: true,
        handler(v) {
          const me = this;
          if (me.isUp) {
            // fileList更新时会触发，这里通过标识符避免重复触发事件
            me.isUp = false;
          } else {
            // 通过标识判断是否是通过value来处理显示值
            let list = [];
            if (!isEmpty(v)) {
              list = v.split(',');
            }
            // 设置文件列表
            me.fileList = me.getList(list);
          }
        }
      },
      // 监听允许上传类型生成校验规则
      acceptType: {
        immediate: true,
        handler(v) {
          if (!isEmpty(v)) {
            // 根据允许上传类型确定校验规则文案
            const me = this,
              rules = me.rules;
            let accept, checkList;
            // 循环遍历生成校验规则数据
            v.forEach((name) => {
              const item = find(rules, { name });
              if (item) {
                // 上传可选类型
                accept = concat(accept, item.suffix);
                // 可选类型校验规则
                checkList = concat(checkList, item.rule);
              }
            });
            accept = compact(accept).toString();
            // 设置可选类型
            me.accept = accept;
            // 设置校验规则
            me.acceptRules = checkList;
            // 设置提示文案
            me.acceptMessage = `只能上传${accept.replace(/\./g, '').replace(/\,/g, '、')}格式文件`;
          }
        }
      },
      // 监听文件列表变化
      fileList(v) {
        // console.log("updateValue");
        const me = this,
          data = map(v, ({ url }) => {
            return this.formatUrl(url);
          });
        me.images = data;
        // 标识当前正在设置数据
        me.isUp = true;
        // 标识当前正在设置数据
        // 更新数据
        me.$emit('updateValue', data.toString());
      }
    },
    filters: {
      formatUrl(v) {
        if (linkVerify(v)) {
          return v;
        }
        return this.oos + v;
      }
    },
    destroyed() {
      this.stopNavigator();
    },
    methods: {
      formatUrl(v) {
        if (linkVerify(v)) {
          return v;
        }
        return this.oos + v;
      },
      /**
       * 上传文件之前进行文件格式校验
       * @param {any} [file] 已选文件
       *
       */
      beforeUpload(file) {
        const me = this,
          is = me.acceptRules.includes(file.type);
        // 文件类型检验
        if (!is) {
          me.$message.error(me.acceptMessage);
          return false;
        }
        // 文件大小校验
        if (file.size > me.maxSize) {
          me.$message.error(`文件大小不能超过${me.sizeFormat(me.maxSize)}`);
          return false;
        }
        // 如果需要处理图片（如上传头像中的剪切）
        if (me.transformFile) {
          me.transformFile(file);
        } else {
          me.upFile(file);
        }
        return is;
      },
      /**
       * 拦截组件自带上传功能，使用阿里云上传
       * #todo 只是上传的话直接用组件上传也是可以的，这里因时间关系用库实现，但是这个库比较大，有待改进
       *
       */
      emptyFn() {},
      // 上传文件
      upFile(file) {
        const me = this;
        // #todo 可以根据配置判断是覆盖还是追加
        oosUpload(file, me.dir).then((data) => {
          if (me.isSingle) {
            me.fileList = [me.getFileDta(data)];
          } else {
            me.fileList.push(me.getFileDta(data));
          }
          me.isShow = false;
        });
      },
      /**
       * 将数据转化为内部可识别数据
       * @param {Array<string>} [list] 待转化数据
       *
       */
      getList(list) {
        const me = this,
          fileShowList = [];
        // 遍历值填充数据
        list.forEach((url) => {
          fileShowList.push(me.getFileDta({ url }));
        });
        return fileShowList;
      },
      /**
       * 获取单个文件数据
       *
       * @param {*} [{
       *     {string}  url 文件路径（必须）
       *
       *     {any} [file] 文件对象
       *
       * }={}]
       */
      getFileDta({ url, file } = {}) {
        // 获取文件后缀
        const me = this,
          rules = me.rules,
          suffix = me.getSuffix(url),
          // 查找文件是否在规则中
          data = find(rules, (info) => {
            return info.suffix.includes(`.${suffix}`);
          }),
          // 获取文件预览图片
          icon = data ? data.name : 'unknown',
          // 获取文件大小
          size = file ? file.size : 0,
          // 获取文件名称
          name = file ? file.name : `${me.fileName}.${suffix}` || url;
        return {
          name: me.getName(name),
          url,
          icon,
          size: me.sizeFormat(size)
        };
      },
      /**
       * 格式化文件大小，将单位从b按规则转化为其他单位
       * @param {number} [value] 待转化数据
       *
       */
      sizeFormat(value) {
        if (!value) {
          return '';
        }
        let unit = '',
          i = 0;
        const k = 1024;
        if (value >= k) {
          // 获取转换下标
          i = Math.floor(Math.log(value) / Math.log(k));
        }
        // 获取转换后单位
        unit = ['b', 'k', 'm', 'g'][i];
        // 根据转换下标获取转换后的值
        value = value / Math.pow(k, i);
        // console.log('第一次转换:', value);
        // 根据配置设置小数精度
        value = floor(value, 1);
        return value + unit;
      },
      /**
       * 根据文件路径获取文件名称
       * @param {string} [url] 文件路径
       *
       */
      getName(url) {
        return url.replace(/(.*\/)*([^.]+)/i, '$2');
      },
      /**
       * 根据文件路径获取文件后缀
       * @param {string} [url] 文件路径
       *
       */
      getSuffix(url) {
        return url.replace(/.+\./, '').toLowerCase();
      },
      /**
       * 删除已上传文件
       * @param {string} [url] 文件路径
       *
       */
      onDelClick({ url }) {
        // 直接移除数据界面不能实时更新，所以用filter
        this.fileList = filter(this.fileList, function(item) {
          return item.url != url;
        });
      },
      // 模板还没有上传到oos中
      onDownTemplateClick() {
        this.$message.warning('暂无模板');
      },
      // 右上角的关闭
      closeCompetence(){
        this.stopNavigator();
       this.isShow=false;
       this.readonly = false;
      },
      // 调用权限（打开摄像头功能）
      getCompetence() {
        this.isShow = true;
        var _this = this;
        _this.readonly = true;
        this.thisCancas = document.getElementById('canvasCamera');
        this.thisContext = this.thisCancas.getContext('2d');
        this.thisVideo = document.getElementById('videoCamera');
        // 旧版本浏览器可能根本不支持mediaDevices，我们首先设置一个空对象
        if (navigator.mediaDevices === undefined) {
          navigator.mediaDevices = {};
        }
        // 一些浏览器实现了部分mediaDevices，我们不能只分配一个对象
        // 使用getUserMedia，因为它会覆盖现有的属性。
        // 这里，如果缺少getUserMedia属性，就添加它。
        if (navigator.mediaDevices.getUserMedia === undefined) {
          navigator.mediaDevices.getUserMedia = function(constraints) {
            // 首先获取现存的getUserMedia(如果存在)
            var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia;
            // 有些浏览器不支持，会返回错误信息
            // 保持接口一致
            if (!getUserMedia) {
              return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }
            // 否则，使用Promise将调用包装到旧的navigator.getUserMedia
            return new Promise(function(resolve, reject) {
              getUserMedia.call(navigator, constraints, resolve, reject);
            });
          };
        }
        var constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' }};
        navigator.mediaDevices
          .getUserMedia(constraints)
          .then(function(stream) {
            // 旧的浏览器可能没有srcObject
            if ('srcObject' in _this.thisVideo) {
              _this.thisVideo.srcObject = stream;
            } else {
              // 避免在新的浏览器中使用它，因为它正在被弃用。
              _this.thisVideo.src = window.URL.createObjectURL(stream);
            }
            _this.thisVideo.onloadedmetadata = function(e) {
              _this.thisVideo.play();
            };
          })
          .catch((err) => {
            console.log(err);
          });
      },
      setImage() {
        var _this = this;
        this.isShow = true;
        // 点击，canvas画图
        _this.readonly = false;
        _this.thisContext.drawImage(_this.thisVideo, 0, 0, _this.videoWidth, _this.videoHeight);
        // 获取图片base64链接
        var image = this.thisCancas.toDataURL('image/png');
        _this.imgSrc = image;
        const file = image;
        const time = new Date().valueOf();
        const name = time + '.png';
        const conversions = this.base64ToFile(file, name);
        _this.upFile(conversions);
      },
      // base64图片转file的方法（base64图片, 设置生成file的文件名）
      base64ToFile(base64, fileName) {
        // 将base64按照 , 进行分割 将前缀  与后续内容分隔开
        const data = base64.split(',');
        // 利用正则表达式 从前缀中获取图片的类型信息（image/png、image/jpeg、image/webp等）
        const type = data[0].match(/:(.*?);/)[1];
        // 从图片的类型信息中 获取具体的文件格式后缀（png、jpeg、webp）
        const suffix = type.split('/')[1];
        // 使用atob()对base64数据进行解码  结果是一个文件数据流 以字符串的格式输出
        const bstr = window.atob(data[1]);
        // 获取解码结果字符串的长度
        let n = bstr.length;
        // 根据解码结果字符串的长度创建一个等长的整形数字数组
        // 但在创建时 所有元素初始值都为 0
        const u8arr = new Uint8Array(n);
        // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
        while (n--) {
          // charCodeAt()：获取给定索引处字符对应的 UTF-16 代码单元
          u8arr[n] = bstr.charCodeAt(n);
        }
        // 利用构造函数创建File文件对象
        // new File(bits, name, options)
        const file = new File([u8arr], `${fileName}.${suffix}`, {
          type: type
        });
        // 将File文件对象返回给方法的调用者
        return file;
      },
      // 关闭摄像头
      stopNavigator() {
        this.thisVideo.srcObject.getTracks()[0].stop();
      }
    }
  };
</script>
<style scoped lang="scss">
  .item-el {
    width: 140px;
    height: 140px;
    border: 1px solid #e8e8e8;
    margin: 0 5px;

    &.text {
      line-height: 1 !important;
    }
  }

  ::v-deep.file-item.upload-item {
    line-height: 0 !important;
  }

  .img-el {
    visibility: hidden;
    position: fixed;
    top: 23%;
    left: 40%;
    &.show {
      visibility: visible;
      z-index:999 ;
    }
    .icon{
      position: absolute;
      top:-10px;
      right: -10px;
      font-size: 30px;
      cursor: pointer;
    }
  }
</style>
